﻿namespace NTLib.WPF.Interactions.Triggers
{
    using NTLib.WPF.Interactions;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Reflection;
    using System.Windows.Input;
    using System.Linq.Expressions;
    using System.Windows.Controls;

    public class EventTrigger : BaseTrigger
    {
        #region Fields

        private EventInfo _event;
        private Delegate _handler;

        #endregion

        #region Properties

        public string EventName
        {
            get { return (string)GetValue(EventNameProperty); }
            set { SetValue(EventNameProperty, value); }
        }

        // Using a DependencyProperty as the backing store for EventName.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EventNameProperty =
            DependencyProperty.Register("EventName", typeof(string), typeof(EventTrigger), new PropertyMetadata(null, (e, a) => (e as EventTrigger).ConnectToEvent()));

        public object Parameter
        {
            get { return (object)GetValue(ParameterProperty); }
            set { SetValue(ParameterProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Parameter.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ParameterProperty =
            DependencyProperty.Register("Parameter", typeof(object), typeof(EventTrigger), new PropertyMetadata(null));

        #endregion

        #region Methods

        /// <summary>
        /// Called when [attached].
        /// </summary>
        /// <param name="obj">The object.</param>
        protected override void OnAttached(DependencyObject obj)
        {
            base.OnAttached(obj) ;
            ConnectToEvent();
        }

        /// <summary>
        /// Connects to event.
        /// </summary>
        private void ConnectToEvent()
        {
            if (this._event != null)
                Disconnect();

            if (this.AttachedObject != null && !string.IsNullOrEmpty(this.EventName))
            {
                this._event = this.AttachedObject.GetType().GetEvent(EventName);
                if (this._event == null)
                    return;
                var expr = (new Action<int, int>(TriggerEventRaised)).Method.GetGenericMethodDefinition();

                var mthdInfo = this._event.EventHandlerType.GetMethod("Invoke");

                var speExpr = expr.MakeGenericMethod(mthdInfo.GetParameters().Select(p => p.ParameterType).ToArray());

                this._handler = Delegate.CreateDelegate(this._event.EventHandlerType, this, speExpr) as Delegate;
                this._event.AddMethod.Invoke(this.AttachedObject, new object[] { this._handler });
            }
        }

        /// <summary>
        /// Called when [detached].
        /// </summary>
        protected override void OnDetached()
        {
            base.OnDetached();
            Disconnect();
        }

        /// <summary>
        /// Disconnects this instance.
        /// </summary>
        private void Disconnect()
        {
            if (this._event != null && this._handler != null)
            {
                this._event.RemoveMethod.Invoke(this.AttachedObject, new object[] { this._handler });
                this._handler = null;
                this._event = null;
            }
        }

        /// <summary>
        /// Triggers the event raised.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void TriggerEventRaised<Arg1, Arg2>(Arg1 sender, Arg2 args)
        {
            this.Raise(this.Parameter);
        }

        #endregion
    }
}
